﻿/* 
 * Copyright (c) 2011 Letwory Interactive
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor: Nathan Letwory
 * 
 */

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Net;
using System.Threading;
using Mono.Math;
using Mono.Security.Cryptography;
using VerseSharp;

namespace VerseSharp
{
    public class Verse: UDPBase
    {
        protected long packetID = -1;
        
        public event Connect0CommandCallback OnConnect0;
        public event Connect1CommandCallback OnConnect1;
        public event Connect2CommandCallback OnConnect2;
        public event ConnectAcceptCallback OnConnectAccept;
        public event ConnectTerminateCallback OnConnectTerminate;
        public event PacketAckCallback OnPacketAck;
        public event PacketNakCallback OnPacketNak;
        public event NodeIndexSubscribeCallback OnNodeIndexSubscribe;
        public event NodeCreateCallback OnNodeCreate;
        public event NodeDestroyCallback OnNodeDestroy;
        public event NodeNameSetCallback OnNodeNameSet;
        public event TagGroupCreateCallback OnTagGroupCreate;
        public event TagGroupDestroyCallback OnTagGroupDestroy;
        public event TagCreateCallback OnTagCreate;
        public event TagDestroyCallback OnTagDestroy;
        public event TagGroupSubscribeCallback OnTagGroupSubscribe;
        public event TagGroupUnsubscribeCallback OnTagGroupUnsubscribe;
        public event GeometryLayerCreateCallback OnGeometryLayerCreate;
        public event GeometryLayerDestroyCallback OnGeometryLayerDestroy;
        public event GeometryLayerSubscribeCallback OnGeometryLayerSubscribe;
        public event GeometryLayerUnsubscribeCallback OnGeometryLayerUnsubscribe;
        public event GeometryVertexSetXYZReal32Callback OnGeometryVertexSetXYZReal32;
        public event GeometryVertexDeleteReal32Callback OnGeometryVertexDeleteReal32;
        public event GeometryVertexSetXYZReal64Callback OnGeometryVertexSetXYZReal64;
        public event GeometryVertexDeleteReal64Callback OnGeometryVertexDeleteReal64;
        public event GeometryVertexSetUInt32Callback OnGeometryVertexSetUInt32;
        public event GeometryVertexSetReal32Callback OnGeometryVertexSetReal32;
        public event GeometryVertexSetReal64Callback OnGeometryVertexSetReal64;
        public event GeometryPolygonSetCornerUInt32Callback OnGeometryPolygonSetCornerUInt32;
        public event GeometryPolygonSetCornerReal32Callback OnGeometryPolygonSetCornerReal32;
        public event GeometryPolygonSetCornerReal64Callback OnGeometryPolygonSetCornerReal64;
        public event GeometryPolygonSetFaceUInt8Callback OnGeometryPolygonSetFaceUInt8;
        public event GeometryPolygonSetFaceUInt32Callback OnGeometryPolygonSetFaceUInt32;
        public event GeometryPolygonSetFaceReal64Callback OnGeometryPolygonSetFaceReal64;
        public event GeometryPolygonSetFaceReal32Callback OnGeometryPolygonSetFaceReal32;
        public event GeometryPolygonDeleteCallback OnGeometryPolygonDelete;
        public event GeometryCreaseSetVertexCallback OnGeometryCreaseSetVertex;
        public event GeometryCreaseSetEdgeCallback OnGeometryCreaseSetEdge;
        public event GeometryBoneCreateCallback onGeometryBoneCreate;
        public event GeometryBoneDestroyCallback onGeometryBoneDestroy;
        public event ObjectTransformSubscribeCallback OnObjectTransformSubscribe;
        public event ObjectTransformPosReal32Callback OnObjectTransformPosReal32;
        public event ObjectTransformRotReal32Callback OnObjectTransformRotReal32;
        public event ObjectTransformScaleReal32Callback OnObjectTransformScaleReal32;
        public event ObjectTransformPosReal64Callback OnObjectTransformPosReal64;
        public event ObjectTransformRotReal64Callback OnObjectTransformRotReal64;
        public event ObjectTransformScaleReal64Callback OnObjectTransformScaleReal64;
        public event ObjectLightSetCallback OnObjectLightSet;
        public event ObjectLinkSetCallback OnObjectLinkSet;
        public event ObjectMethodGroupCreateCallback OnObjectMethodGroupCreate;
        public event ObjectMethodGroupDestroyCallback OnObjectMethodGroupDestroy;
        public event ObjectMethodGroupSubscribeCallback OnObjectMethodGroupSubscribe;
        public event ObjectMethodCreateCallback OnObjectMethodCreate;
        public event ObjectMethodDestroyCallback OnObjectMethodDestroy;
        public event ObjectMethodCallCallback OnObjectMethodCall;
        public event ObjectHideCallback OnObjectHide;
        public event ObjectAnimRunCallback OnObjectAnimRun;
        public event MaterialFragmentCreateCallback OnMaterialFragmentCreate;
        public event MaterialFragmentDestroyCallback OnMaterialFragmentDestroy;
        public event BitmapLayerCreateCallback OnBitmapLayerCreate;
        public event BitmapLayerCreateCallback OnBitmapLayerDestroy;
        public event BitmapDimensionsSetCallback OnBitmapDimensionsSet;
        public event BitmapTileSetCallback OnBitmapTileSet;
        public event BitmapLayerSubscribeCallback OnBitmapLayerSubscribe;
        public event BitmapLayerUnsubscribeCallback OnBitmapLayerUnsubscribe;
        
        private IDictionary<IPEndPoint, VerseSession> sessions;
        
        
        public Verse(IPEndPoint verseServer)
            : base(verseServer)
        {
            if(verseServer == null) System.Console.WriteLine("ooh, server implementation");
            sessions = new Dictionary<IPEndPoint, VerseSession>();
            OnPacketAck += PacketAckHandler;
        }
        
        public VerseSession CreateSession(IPEndPoint endpoint, string username, string password)
        {
            VerseSession vs = new VerseSession(this, endpoint, username, password);
            sessions[endpoint] = vs;
            return vs;
        }
        
        public void SetRemoteEndpoint(IPEndPoint ep)
        {
            base.SetRemoteEndPoint(ep);
        }
        
        public new void Stop()
        {
            // FIXME: Logout

            base.Stop();
        }
        
        public void Start()
        {
            base.Start(IPAddress.Any);
        }
        
        /// <summary>
        /// </summary>
        /// <param name="command"></param>
        /// <remarks>need to use versesession endpoint</remarks>
        public void SendCommand(VerseSession vs, IVerseCommand command)
        {
            VersePacket packet = new VersePacket();
            packet.Command = command;

            if (command.ID < CommandID.PacketAck || command.ID > CommandID.PacketNak)
                Console.WriteLine(String.Format("Sending {0}", command.ID));

            // Reset to zero if we've hit the upper sequence number limit
            Interlocked.CompareExchange(ref packetID, 0, UInt32.MaxValue);
            // Increment and fetch the current sequence number
            packet.ID = (uint)Interlocked.Increment(ref packetID);

            UDPPacketBuffer buffer = new UDPPacketBuffer(vs.VerseServer);

            // TODO: Queue up outgoing commands and batch together into 1500 byte packets

            int pos = 0;
            // Serialize the packet ID
            Utils.PutUInt32(buffer.Data, ref pos, packet.ID);
            // Serialize the command ID
            Utils.PutUInt8(buffer.Data, ref pos, (byte)packet.Command.ID);
            // Serialize the command data
            packet.Command.Serialize(buffer.Data, ref pos);
            buffer.DataLength = pos;
            // Encrypt the command
            EncryptCommand(vs, packet.ID, buffer, 4, pos - 4);

            base.AsyncBeginSend(buffer);
        }
        
        protected void EncryptCommand(VerseSession vs, uint packetID, UDPPacketBuffer buffer, int startPos, int size)
        {
            int pos = vs.xorKey[packetID % VerseConstants.ENCRYPTION_KEY_SIZE] + startPos;

            for (int i = 0; i < size; i++)
                buffer.Data[startPos + i] ^= vs.xorKey[(i + pos) % VerseConstants.ENCRYPTION_KEY_SIZE];
        }

        protected void DecryptPacket(VerseSession vs, UDPPacketBuffer buffer)
        {
            uint packetID = DataConverter.BigEndian.GetUInt32(buffer.Data, 0);
            int pos = vs.xorKey[packetID % VerseConstants.ENCRYPTION_KEY_SIZE];

            for (int i = 4; i < buffer.DataLength; i++)
                buffer.Data[i] ^= vs.xorKey[(i + pos) % VerseConstants.ENCRYPTION_KEY_SIZE];
        }

        protected void SendAck(VerseSession vs, uint packetID)
        {
            //LogAdapter.Debug("Acking packet " + packetID.ToString());

            PacketAckCommand ack = new PacketAckCommand();
            ack.PacketID = packetID;
            SendCommand(vs, ack);
        }

        protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent)
        {
        }

        protected override void PacketReceived(UDPPacketBuffer buffer)
        {
            if(!sessions.ContainsKey((IPEndPoint)(buffer.RemoteEndPoint))) return;
            
            VerseSession vs = sessions[(IPEndPoint)(buffer.RemoteEndPoint)];
            DecryptPacket(vs, buffer);

            int pos = 0;
            VersePacket packet = new VersePacket();
            packet.ID = DataConverter.BigEndian.GetUInt32(buffer.Data, pos);
            pos += 4;

            if (vs.xorKeySet)
                SendAck(vs, packet.ID);

            while (pos < buffer.DataLength)
            {
                CommandID commandID = (CommandID)buffer.Data[pos++];
                if (!DecodeCommand(packet.ID, commandID, buffer, ref pos))
                    break;
            }
        }
        
        private bool DecodeCommand(uint packetID, CommandID commandID, UDPPacketBuffer buffer, ref int pos)
        {
            /*if (commandID != CommandID.PacketAck && commandID != CommandID.PacketNak)
                Console.WriteLine(String.Format("Session: {0} . Packet: {1} . Command: {2}", avatarID, packetID, commandID));*/
            switch (commandID)
            {
                // System commands
                case CommandID.Connect:
                    ConnectCommand connect = new ConnectCommand();
                    connect.Deserialize(buffer.Data, ref pos);

                    if (connect.Stage == 0)
                    {
                        if (OnConnect0 != null)
                            OnConnect0(connect);
                    }
                    else if (connect.Stage == 1)
                    {
                        if (OnConnect1 != null)
                            OnConnect1(connect);
                    }
                    else if (connect.Stage == 2)
                    {
                        if (OnConnect2 != null)
                            OnConnect2(connect);
                    }

                    break;
                case CommandID.ConnectAccept:
                    ConnectAcceptCommand accept = new ConnectAcceptCommand();
                    accept.Deserialize(buffer.Data, ref pos);
                    if (OnConnectAccept != null)
                        OnConnectAccept(accept);
                    break;
                case CommandID.ConnectTerminate:
                    ConnectTerminateCommand terminate = new ConnectTerminateCommand();
                    terminate.Deserialize(buffer.Data, ref pos);
                    // TODO handle properly in server usage (resource freeing etc)
                    // note, it looks like reference implementation server never successfully
                    // manages to send the termination command to the client that initiated it
                    if (OnConnectTerminate != null)
                        OnConnectTerminate(/*this*/ null, terminate);
                    break;
                case CommandID.PacketAck:
                    PacketAckCommand ack = new PacketAckCommand();
                    ack.Deserialize(buffer.Data, ref pos);
                    if (OnPacketAck != null)
                        OnPacketAck(ack.PacketID);
                    break;
                case CommandID.PacketNak:
                    PacketNakCommand nak = new PacketNakCommand();
                    nak.Deserialize(buffer.Data, ref pos);
                    if (OnPacketNak != null)
                        OnPacketNak(nak.PacketID);
                    break;
                case CommandID.NodeIndexSubscribe:
                    NodeIndexSubscribeCommand indexSubscribe = new NodeIndexSubscribeCommand();
                    indexSubscribe.Deserialize(buffer.Data, ref pos);
                    if (OnNodeIndexSubscribe != null)
                        OnNodeIndexSubscribe(indexSubscribe);
                    break;
                case CommandID.NodeCreate:
                    NodeCreateCommand nodeCreate = new NodeCreateCommand();
                    nodeCreate.Deserialize(buffer.Data, ref pos);
                    if (!nodeCreate.Destroy)
                    {
                        if (OnNodeCreate != null)
                            OnNodeCreate(nodeCreate);
                    }
                    else
                    {
                        if (OnNodeDestroy != null)
                            OnNodeDestroy(nodeCreate);
                    }
                    break;
                case CommandID.NodeNameSet:
                    NodeNameSetCommand nodeNameSet = new NodeNameSetCommand();
                    nodeNameSet.Deserialize(buffer.Data, ref pos);
                    if (OnNodeNameSet != null)
                        OnNodeNameSet(nodeNameSet);
                    break;
                case CommandID.TagGroupCreate:
                    TagGroupCreateCommand tagGroupCreate = new TagGroupCreateCommand();
                    tagGroupCreate.Deserialize(buffer.Data, ref pos);
                    if (String.IsNullOrEmpty(tagGroupCreate.Name))
                    {
                        if (OnTagGroupDestroy != null)
                        {
                            OnTagGroupDestroy(tagGroupCreate);
                        }
                    }
                    else
                    {
                        if (OnTagGroupCreate != null)
                        {
                            OnTagGroupCreate(tagGroupCreate);
                        }
                    }
                    break;
                case CommandID.TagGroupSubscribe:
                    TagGroupSubscribeCommand tagGroubSubscribe = new TagGroupSubscribeCommand();
                    tagGroubSubscribe.Deserialize(buffer.Data, ref pos);
                    if (tagGroubSubscribe.Subscribed)
                    {
                        if (OnTagGroupSubscribe != null)
                        {
                            OnTagGroupSubscribe(tagGroubSubscribe);
                        }
                    }
                    else
                    {
                        if (OnTagGroupUnsubscribe != null)
                        {
                            OnTagGroupUnsubscribe(tagGroubSubscribe);
                        }
                    }
                    break;
                case CommandID.TagCreate:
                    TagCreateCommand tagCreate = new TagCreateCommand();
                    tagCreate.Deserialize(buffer.Data, ref pos);
                    if (tagCreate.Type > TagType.TypeCount)
                    {
                        if (OnTagDestroy != null)
                        {
                            OnTagDestroy(tagCreate);
                        }
                    }
                    else
                    {
                        if (OnTagCreate != null)
                        {
                            OnTagCreate(tagCreate);
                        }
                    }
                    break;
                case CommandID.GeometryLayerCreate:
                    GeometryLayerCreateCommand geometryCreate = new GeometryLayerCreateCommand();
                    geometryCreate.Deserialize(buffer.Data, ref pos);
                    if (String.IsNullOrEmpty(geometryCreate.LayerName))
                    {
                        if (OnGeometryLayerDestroy != null)
                            OnGeometryLayerDestroy(geometryCreate);
                    }
                    else
                    {
                        if (OnGeometryLayerCreate != null)
                            OnGeometryLayerCreate(geometryCreate);
                    }
                    break;
                case CommandID.GeometryLayerSubscribe:
                    GeometryLayerSubscribeCommand geometryLayerSubscribe = new GeometryLayerSubscribeCommand();
                    geometryLayerSubscribe.Deserialize(buffer.Data, ref pos);
                    if (geometryLayerSubscribe.Subscribed)
                    {
                        if (OnGeometryLayerSubscribe != null)
                            OnGeometryLayerSubscribe(geometryLayerSubscribe);
                    }
                    else
                    {
                        if (OnGeometryLayerUnsubscribe != null)
                            OnGeometryLayerUnsubscribe(geometryLayerSubscribe);
                    }
                    break;
                case CommandID.GeometryVertexSetXYZReal32:
                    GeometryVertexSetXYZReal32Command vertexSetXYZReal32 = new GeometryVertexSetXYZReal32Command();
                    vertexSetXYZReal32.Deserialize(buffer.Data, ref pos);
                    if (vertexSetXYZReal32.X == float.MaxValue ||
                        vertexSetXYZReal32.Y == float.MaxValue ||
                        vertexSetXYZReal32.Z == float.MaxValue)
                    {
                        if (OnGeometryVertexDeleteReal32 != null)
                            OnGeometryVertexDeleteReal32(vertexSetXYZReal32);
                    }
                    else
                    {
                        if (OnGeometryVertexSetXYZReal32 != null)
                            OnGeometryVertexSetXYZReal32(vertexSetXYZReal32);
                    }
                    break;
                case CommandID.GeometryVertexSetXYZReal64:
                    GeometryVertexSetXYZReal64Command vertexSetXYZReal64 = new GeometryVertexSetXYZReal64Command();
                    vertexSetXYZReal64.Deserialize(buffer.Data, ref pos);
                    if (vertexSetXYZReal64.X == double.MaxValue ||
                        vertexSetXYZReal64.Y == double.MaxValue ||
                        vertexSetXYZReal64.Z == double.MaxValue)
                    {
                        if (OnGeometryVertexDeleteReal64 != null)
                            OnGeometryVertexDeleteReal64(vertexSetXYZReal64);
                    }
                    else
                    {
                        if (OnGeometryVertexSetXYZReal64 != null)
                            OnGeometryVertexSetXYZReal64(vertexSetXYZReal64);
                    }
                    break;
                case CommandID.GeometryVertexSetUInt32:
                    GeometryVertexSetUInt32Command vertexSetUInt32 = new GeometryVertexSetUInt32Command();
                    vertexSetUInt32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryVertexSetUInt32 != null)
                    {
                        OnGeometryVertexSetUInt32(vertexSetUInt32);
                    }
                    break;
                case CommandID.GeometryVertexSetReal32:
                    GeometryVertexSetReal32Command vertexSetReal32 = new GeometryVertexSetReal32Command();
                    vertexSetReal32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryVertexSetReal32 != null)
                    {
                        OnGeometryVertexSetReal32(vertexSetReal32);
                    }
                    break;
                case CommandID.GeometryVertexSetReal64:
                    GeometryVertexSetReal64Command vertexSetReal64 = new GeometryVertexSetReal64Command();
                    vertexSetReal64.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryVertexSetReal64 != null)
                    {
                        OnGeometryVertexSetReal64(vertexSetReal64);
                    }
                    break;
                case CommandID.GeometryPolygonSetCornerUInt32:
                    GeometryPolygonSetCornerUInt32Command polygonSetCornerUInt32 = new GeometryPolygonSetCornerUInt32Command();
                    polygonSetCornerUInt32.Deserialize(buffer.Data, ref pos);
                    if (polygonSetCornerUInt32.LayerID == 1 && polygonSetCornerUInt32.V0 == uint.MaxValue)
                    {
                        if (OnGeometryPolygonDelete != null)
                        {
                            OnGeometryPolygonDelete(polygonSetCornerUInt32);
                        }
                    }
                    else
                    {
                        if (OnGeometryPolygonSetCornerUInt32 != null)
                        {
                            OnGeometryPolygonSetCornerUInt32(polygonSetCornerUInt32);
                        }
                    }
                    break;
                case CommandID.GeometryPolygonSetCornerReal32:
                    GeometryPolygonSetCornerReal32Command polygonSetCornerReal32 = new GeometryPolygonSetCornerReal32Command();
                    polygonSetCornerReal32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetCornerReal32 != null)
                    {
                        OnGeometryPolygonSetCornerReal32(polygonSetCornerReal32);
                    }
                    break;
                case CommandID.GeometryPolygonSetCornerReal64:
                    GeometryPolygonSetCornerReal64Command polygonSetCornerReal64 = new GeometryPolygonSetCornerReal64Command();
                    polygonSetCornerReal64.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetCornerReal64 != null)
                    {
                        OnGeometryPolygonSetCornerReal64(polygonSetCornerReal64);
                    }
                    break;
                case CommandID.GeometryPolygonSetFaceUInt8:
                    GeometryPolygonSetFaceUInt8Command polygonSetFaceUInt8 = new GeometryPolygonSetFaceUInt8Command();
                    polygonSetFaceUInt8.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetFaceUInt8 != null)
                    {
                        OnGeometryPolygonSetFaceUInt8(polygonSetFaceUInt8);
                    }
                    break;
                case CommandID.GeometryPolygonSetFaceUInt32:
                    GeometryPolygonSetFaceUInt32Command polygonSetFaceUInt32 = new GeometryPolygonSetFaceUInt32Command();
                    polygonSetFaceUInt32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetFaceUInt32 != null)
                    {
                        OnGeometryPolygonSetFaceUInt32(polygonSetFaceUInt32);
                    }
                    break;
                case CommandID.GeometryPolygonSetFaceReal64:
                    GeometryPolygonSetFaceReal64Command polygonSetFaceReal64 = new GeometryPolygonSetFaceReal64Command();
                    polygonSetFaceReal64.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetFaceReal64 != null)
                    {
                        OnGeometryPolygonSetFaceReal64(polygonSetFaceReal64);
                    }
                    break;
                case CommandID.GeometryPolygonSetFaceReal32:
                    GeometryPolygonSetFaceReal32Command polygonSetFaceReal32 = new GeometryPolygonSetFaceReal32Command();
                    polygonSetFaceReal32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetFaceReal32 != null)
                    {
                        OnGeometryPolygonSetFaceReal32(polygonSetFaceReal32);
                    }
                    break;
                case CommandID.GeometryCreaseSetVertex:
                    GeometryCreaseSetVertexCommand geometryCreaseSetVertex = new GeometryCreaseSetVertexCommand();
                    geometryCreaseSetVertex.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryCreaseSetVertex != null)
                        OnGeometryCreaseSetVertex(geometryCreaseSetVertex);
                    break;
                case CommandID.GeometryCreaseSetEdge:
                    GeometryCreaseSetEdgeCommand geometryCreaseSetEdge = new GeometryCreaseSetEdgeCommand();
                    geometryCreaseSetEdge.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryCreaseSetEdge != null)
                        OnGeometryCreaseSetEdge(geometryCreaseSetEdge);
                    break;
                case CommandID.GeometryBoneCreate:
                    GeometryBoneCreateCommand boneCreate = new GeometryBoneCreateCommand();
                    boneCreate.Deserialize(buffer.Data, ref pos);
                    if (String.IsNullOrEmpty(boneCreate.Weight))
                    {
                        if (onGeometryBoneDestroy != null)
                        {
                            onGeometryBoneDestroy(boneCreate);
                        }
                    }
                    else
                    {
                        if (onGeometryBoneCreate != null)
                        {
                            onGeometryBoneCreate(boneCreate);
                        }
                    }
                    break;
                case CommandID.ObjectTransformSubscribe:
                    ObjectTransformSubscribeCommand objectTransformSubscribe = new ObjectTransformSubscribeCommand();
                    objectTransformSubscribe.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformSubscribe != null)
                        OnObjectTransformSubscribe(objectTransformSubscribe);
                    break;
                case CommandID.ObjectTransformPosReal32:
                    ObjectTransformPosReal32Command objectTransformPosReal32 = new ObjectTransformPosReal32Command();
                    objectTransformPosReal32.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformPosReal32 != null)
                        OnObjectTransformPosReal32(objectTransformPosReal32);
                    break;
                case CommandID.ObjectTransformPosReal64:
                    ObjectTransformPosReal64Command objectTransformPosReal64 = new ObjectTransformPosReal64Command();
                    objectTransformPosReal64.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformPosReal64 != null)
                        OnObjectTransformPosReal64(objectTransformPosReal64);
                    break;
                case CommandID.ObjectTransformRotReal32:
                    ObjectTransformRotReal32Command objectTransformRotReal32 = new ObjectTransformRotReal32Command();
                    objectTransformRotReal32.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformRotReal32 != null)
                        OnObjectTransformRotReal32(objectTransformRotReal32);
                    break;
                case CommandID.ObjectTransformRotReal64:
                    ObjectTransformRotReal64Command objectTransformRotReal64 = new ObjectTransformRotReal64Command();
                    objectTransformRotReal64.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformRotReal64 != null)
                        OnObjectTransformRotReal64(objectTransformRotReal64);
                    break;
                case CommandID.ObjectTransformScaleReal32:
                    ObjectTransformScaleReal32Command objectTransformScaleReal32 = new ObjectTransformScaleReal32Command();
                    objectTransformScaleReal32.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformScaleReal32 != null)
                        OnObjectTransformScaleReal32(objectTransformScaleReal32);
                    break;
                case CommandID.ObjectTransformScaleReal64:
                    ObjectTransformScaleReal64Command objectTransformScaleReal64 = new ObjectTransformScaleReal64Command();
                    objectTransformScaleReal64.Deserialize(buffer.Data, ref pos);
                    if (OnObjectTransformScaleReal64 != null)
                        OnObjectTransformScaleReal64(objectTransformScaleReal64);
                    break;
                case CommandID.ObjectLightSet:
                    ObjectLightSetCommand objectLightSet = new ObjectLightSetCommand();
                    objectLightSet.Deserialize(buffer.Data, ref pos);
                    if (OnObjectLightSet != null)
                        OnObjectLightSet(objectLightSet);
                    break;
                case CommandID.ObjectLinkSet:
                    ObjectLinkSetCommand objectLinkSet = new ObjectLinkSetCommand();
                    objectLinkSet.Deserialize(buffer.Data, ref pos);
                    if (OnObjectLinkSet != null)
                        OnObjectLinkSet(objectLinkSet);
                    break;
                case CommandID.ObjectMethodGroupCreate:
                    ObjectMethodGroupCreateCommand objectMethodGroupCreate = new ObjectMethodGroupCreateCommand();
                    objectMethodGroupCreate.Deserialize(buffer.Data, ref pos);
                    if (objectMethodGroupCreate.Destroy)
                    {
                        if (OnObjectMethodGroupDestroy != null)
                            OnObjectMethodGroupDestroy(objectMethodGroupCreate);
                    }
                    else
                    {
                        if (OnObjectMethodGroupCreate != null)
                            OnObjectMethodGroupCreate(objectMethodGroupCreate);
                    }
                    break;
                case CommandID.ObjectMethodGroupSubscribe:
                    ObjectMethodGroupSubscribeCommand objectMethodGroupSubscribe = new ObjectMethodGroupSubscribeCommand();
                    objectMethodGroupSubscribe.Deserialize(buffer.Data, ref pos);
                    if (OnObjectMethodGroupSubscribe != null)
                        OnObjectMethodGroupSubscribe(objectMethodGroupSubscribe);
                    break;
                case CommandID.ObjectMethodCreate:
                    ObjectMethodCreateCommand objectMethodCreate = new ObjectMethodCreateCommand();
                    objectMethodCreate.Deserialize(buffer.Data, ref pos);
                    if (objectMethodCreate.Destroy)
                    {
                        if (OnObjectMethodDestroy != null)
                            OnObjectMethodDestroy(objectMethodCreate);
                    }
                    else
                    {
                        if (OnObjectMethodCreate != null)
                            OnObjectMethodCreate(objectMethodCreate);
                    }
                    break;
                case CommandID.ObjectMethodCall:
                    ObjectMethodCallCommand objectMethodCall = new ObjectMethodCallCommand();
                    objectMethodCall.Deserialize(buffer.Data, ref pos);
                    if (OnObjectMethodCall != null)
                        OnObjectMethodCall(objectMethodCall);
                    break;
                case CommandID.ObjectAnimRun:
                    ObjectAnimRunCommand objectAnimRun = new ObjectAnimRunCommand();
                    objectAnimRun.Deserialize(buffer.Data, ref pos);
                    if (OnObjectAnimRun != null)
                        OnObjectAnimRun(objectAnimRun);
                    break;
                case CommandID.ObjectHide:
                    ObjectHideCommand objectHide = new ObjectHideCommand();
                    objectHide.Deserialize(buffer.Data, ref pos);
                    if (OnObjectHide != null)
                        OnObjectHide(objectHide);
                    break;
                case CommandID.MaterialFragmentCreate:
                    MaterialFragmentCreateCommand materialCreate = new MaterialFragmentCreateCommand();
                    materialCreate.Deserialize(buffer.Data, ref pos);
                    if (materialCreate.FragmentType == (FragmentType)byte.MaxValue)
                    {
                        if (OnMaterialFragmentDestroy != null)
                        {
                            OnMaterialFragmentDestroy(materialCreate);
                        }
                    }
                    else
                    {
                        if (OnMaterialFragmentCreate != null)
                        {
                            OnMaterialFragmentCreate(materialCreate);
                        }
                    }
                    break;
                case CommandID.BitmapDimensionsSet:
                    BitmapDimensionsSetCommand bitmapDimensionsSet = new BitmapDimensionsSetCommand();
                    bitmapDimensionsSet.Deserialize(buffer.Data, ref pos);
                    if (OnBitmapDimensionsSet != null)
                    {
                        OnBitmapDimensionsSet(bitmapDimensionsSet);
                    }
                    break;
                case CommandID.BitmapLayerCreate:
                    BitmapLayerCreateCommand bitmapLayerCreate = new BitmapLayerCreateCommand();
                    bitmapLayerCreate.Deserialize(buffer.Data, ref pos);
                    if (bitmapLayerCreate.Type == (BitmapLayerType)byte.MaxValue)
                    {
                        if (OnBitmapLayerDestroy != null)
                        {
                            OnBitmapLayerDestroy(bitmapLayerCreate);
                        }
                    }
                    else
                    {
                        if (OnBitmapLayerCreate != null)
                        {
                            OnBitmapLayerCreate(bitmapLayerCreate);
                        }
                    }
                    break;
                case CommandID.BitmapTileSet:
                    BitmapTileSetCommand bitmapTileSet = new BitmapTileSetCommand();
                    bitmapTileSet.Deserialize(buffer.Data, ref pos);
                    if (OnBitmapTileSet != null)
                    {
                        OnBitmapTileSet(bitmapTileSet);
                    }
                    break;
                case CommandID.BitmapLayerSubscribe:
                    BitmapLayerSubscribeCommand bitmapLayerSubscribe = new BitmapLayerSubscribeCommand();
                    bitmapLayerSubscribe.Deserialize(buffer.Data, ref pos);
                    if (bitmapLayerSubscribe.Level == byte.MaxValue)
                    {
                        if (OnBitmapLayerUnsubscribe != null)
                        {
                            OnBitmapLayerUnsubscribe(bitmapLayerSubscribe);
                        }
                    }
                    else {
                        if (OnBitmapLayerSubscribe != null)
                        {
                            OnBitmapLayerSubscribe(bitmapLayerSubscribe);
                        }
                    }
                    break;
                default:
                    LogAdapter.Warn("Unrecognized packet command: " + commandID.ToString());
                    Console.WriteLine("Unrecognized command: {0}", commandID);
                    return false;
            }

            return true;
        }

        private void PacketAckHandler(uint packetID)
        {
            // TODO:
            //LogAdapter.Debug("Received a PacketAck for " + packetID.ToString());
        }

        private void PacketNakHandler(uint packetID)
        {
            // TODO:
            //LogAdapter.Debug("Received a PacketNak for " + packetID.ToString());
        }
    }
}
